package com.ds.robot.assistant.job;

import cn.hutool.core.thread.ThreadUtil;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Lists;
import com.ds.robot.assistant.component.MessageSender;
import com.ds.robot.assistant.entity.DutyInfo;
import com.ds.robot.assistant.enums.Env;
import com.ds.robot.assistant.service.DutyInfoService;
import com.ds.robot.assistant.service.HolidayServiceContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

/**
 * 作用：这个是新版本
 *
 * @author WeiShaoying
 * 
 */
@Component("dutyJob")
@Slf4j
public class DutyNewJob extends BaseJob {

    private static final DateTimeFormatter DAY_FORMATTER = DateTimeFormatter.ofPattern("yyyy年M月d日");
    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    @Autowired
    private DutyInfoService dutyInfoService;

    @Autowired
    private HolidayServiceContext holidayServiceContext;

    @Autowired
    private MessageSender messageSender;

    private final LoadingCache<String, Boolean> loadingCache;

    public DutyNewJob() {
        // 构建一个缓存，最大容量1000，过期时间为1分钟
        loadingCache = CacheBuilder.newBuilder()
                .maximumSize(1000)
                .expireAfterWrite(1, TimeUnit.HOURS)
                .build(new CacheLoader<String, Boolean>() {
                    @Override
                    public Boolean load(String key) {
                        return false; // 默认值，表示未
                    }
                });
    }

    // 预先提醒 每天上午执行一次
    @Scheduled(cron = "0 0 10 * * ?")
    public void executePreTip() {
        ((DutyNewJob) AopContext.currentProxy()).executePreTip(null);
    }

    @Transactional(rollbackFor = Exception.class)
    public void executePreTip(LocalDate localDate) {
        boolean isProd = false;
        LocalDate today = LocalDate.now();
        if (localDate != null) {
            today = localDate;
        }else {
            isProd = true;
        }
        // 从明天开始计算
        LocalDate date = today.plusDays(1);
        List<LocalDate> dateList = Lists.newArrayList();
        while (holidayServiceContext.isRestDay(date.toString())) {
            dateList.add(date);
            date = date.plusDays(1);
            // 这个接口QPS不能超过1或2，否则会被限流
            ThreadUtil.sleep(1000);
        }
        // 此时得到了所有的休息日
        if (dateList.isEmpty()) {
            log.warn("明天是工作日不需要预先提醒");
            return;
        }
        Long preCount = dutyInfoService.lambdaQuery()//.eq(DutyInfo::getDutyStatus, 0)
                .eq(DutyInfo::getPreTipStatus, 1)
                .eq(DutyInfo::getPreTipDate, dateList.get(0))
                .count();
        log.info("dateList={}, preCount={}", dateList, preCount);
        if (preCount > 0) {
            log.info("已经发过预先提醒，程序结束");
            return;
        }
        int count = dateList.size() * 2;
        // 查询没有值班且没有提醒过的
        List<DutyInfo> dutyInfoList = dutyInfoService.lambdaQuery().eq(DutyInfo::getDutyStatus, 0)
                .eq(DutyInfo::getPreTipStatus, 0)
                .orderByAsc(DutyInfo::getId)
                .last("limit " + count)
                .list();
        if (!dutyInfoList.isEmpty()) {
            // 每个日期，对应上午，下午 2个值班人员
            StringBuilder builder = new StringBuilder();
            builder.append("# [研发部休息日值班预先提醒]\n");
            int k = 0;
            boolean success = true;
            try {
                for (LocalDate dutyDate : dateList) {
                    DutyInfo morningPerson = dutyInfoList.get(k++);
                    String morningUserid = morningPerson.getUserid();
                    String morningName = morningPerson.getName();
                    DutyInfo afternoonPerson = dutyInfoList.get(k++);
                    String afternoonUserId = afternoonPerson.getUserid();
                    String afternoonName = afternoonPerson.getName();
                    String dayOfWeek = getDayOfWeek(dutyDate);
                    log.debug("{}={}", dutyDate, dayOfWeek);

                    builder.append(DAY_FORMATTER.format(dutyDate)).append(" （").append(dayOfWeek).append("）")
                            .append("\t<font color=\"warning\">**上午：**</font><font color=\"info\">**").append(morningName).append("**</font> **")
                            .append(super.getNoticeStr(morningUserid))
                            .append("**，<font color=\"warning\">**下午：**</font><font color=\"info\">**").append(afternoonName).append("**</font> **")
                            .append(super.getNoticeStr(afternoonUserId))
                            .append("** 值班\n");
                    if (isProd) {
                        boolean update = dutyInfoService.lambdaUpdate().in(DutyInfo::getId, Arrays.asList(morningPerson.getId(), afternoonPerson.getId()))
                                .set(DutyInfo::getPreTipStatus, 1)
                                .set(DutyInfo::getPreTipDate, dutyDate)
                                .update();
                        log.info("更新({},{})预先提醒执行成功 = {}", morningPerson.getId(), afternoonPerson.getId(), update);
                    }
                }
                builder.append("\n\n请以上同学重点关注~");
                builder.append("\n\n\n<@wjn> <@w>");
                log.debug("预先提醒={}", builder);
            } catch (Exception e) {
                success = false;
                log.error("预先提醒失败", e);
            }
            if (success) {
                messageSender.sendMessage(builder.toString(), Env.TEST);
                if (isProd) {
                    messageSender.sendMessage(builder.toString(), Env.PROD);
                }
            }
        }
    }


    @Scheduled(cron = "0 0/40 6 * * ?")
    public void executeTest() {
        ((DutyNewJob) AopContext.currentProxy()).execute(Env.TEST);
    }

    // 每天早上7点，每隔20分钟执行一次
    @Scheduled(cron = "0 0/40 7 * * ?")
    public void executeProd() {
        ((DutyNewJob) AopContext.currentProxy()).execute(Env.PROD);
    }

    @Transactional(rollbackFor = Exception.class)
    public void execute(Env env) {
        if (holidayServiceContext.isWorkDay(LocalDate.now().toString())) {
            log.info("今天是工作日，不发值班提醒");
            return;
        }
        String key = env.name() + LocalDate.now();
        try {
            if (!loadingCache.get(key)) {
                log.info("===={}新值班定时任务开始：{}", env, FORMATTER.format(LocalDateTime.now()));
                // 查询当前状态为未值班的人按照id排序，取前2个
                List<DutyInfo> list = dutyInfoService.lambdaQuery().eq(DutyInfo::getDutyStatus, 0)
                        .orderByAsc(DutyInfo::getId)
                        .last("limit 2")
                        .list();
                // 拼装要发送的文本，并发送提醒
                if (!list.isEmpty() && list.size() > 1) {
                    DutyInfo dutyMorning = list.get(0);
                    DutyInfo dutyAfternoon = list.get(1);
                    String names = super.getNoticeStr(dutyMorning.getUserid()) + "上午，" + super.getNoticeStr(dutyAfternoon.getUserid()) + "下午";
                    String message = buildMessage(names);

                    log.debug(message);
                    messageSender.sendMessage(message, env);
                    // 线程安全地更新缓存
                    loadingCache.put(key, true);

                    // 测试不更新，只有生产更新状态
                    if (env.equals(Env.PROD)) {
                        dutyInfoService.lambdaUpdate().eq(DutyInfo::getId, dutyMorning.getId())
                                .set(DutyInfo::getDutyStatus, 1)
                                .set(DutyInfo::getDutyDate, LocalDate.now())
                                .set(DutyInfo::getDutyPoint, 1)
                                .update();
                        dutyInfoService.lambdaUpdate().eq(DutyInfo::getId, dutyAfternoon.getId())
                                .set(DutyInfo::getDutyStatus, 1)
                                .set(DutyInfo::getDutyDate, LocalDate.now())
                                .set(DutyInfo::getDutyPoint, 2)
                                .update();
                    }
                }
            } else {
                // 已经发送过
                log.info("......已发送过");
            }
        } catch (ExecutionException e) {
            log.error("缓存操作异常", e);
        }
        log.info("===={}值班定时任务完成：{}", env, FORMATTER.format(LocalDateTime.now()));
    }

    private String buildMessage(String personName) {
        // 构建消息体
        StringBuilder builder = new StringBuilder();
        builder.append("# 值班提醒\n")
                .append("温馨提示：").append(LocalDate.now().format(DAY_FORMATTER))
                .append(" <font color=\"warning\">**").append(personName).append("**</font> 值班，辛苦做好群问题的解答和支持工作~\n")
                .append(" > 上午：<font color=\"info\">**08:00-15:00**</font>\n")
                .append(" > 下午：<font color=\"info\">**15:00-22:00**</font>\n")
                .append("\n## 参考文档\n")
                .append(" > [1.店长手册](https://doc.weixin.qq.com/pdf/d3_AWQApgY4ALE73xcxhWGQHqId8LhQh?scode=AG0A3QfYAA0Ilxf3fl)\n")
                .append(" > [2.常见问题收集表](https://doc.weixin.qq.com/sheet/e3_AaMAzwa-AMIaua51arvRFOYV2Th31?scode=AG0A3QfYAA0jAKdaC2)");

        return builder.toString();
    }

    public static void main(String[] args) {
        LocalDate today = LocalDate.now();
        System.out.println("today = " + today.toString());
        System.out.println("tomorrow = " + today.plusDays(1));
    }

    public String getDayOfWeek(LocalDate date) {
        DayOfWeek dayOfWeek = date.getDayOfWeek();
        // 将ISO标准的DayOfWeek转换为中国的DayOfWeek
        // DayOfWeek chinaDayOfWeek = DayOfWeek.of((dayOfWeek.getValue() % 7) + 1);
        switch (dayOfWeek) {
            case MONDAY:
                return "星期一";
            case TUESDAY:
                return "星期二";
            case WEDNESDAY:
                return "星期三";
            case THURSDAY:
                return "星期四";
            case FRIDAY:
                return "星期五";
            case SATURDAY:
                return "星期六";
            case SUNDAY:
                return "星期日";
            default:
                throw new IllegalArgumentException("非法参数");
        }
    }
}
